var colorsToHex = {"aliceblue":"#f0f8ff","antiquewhite":"#faebd7","aqua":"#00ffff","aquamarine":"#7fffd4","azure":"#f0ffff",
"beige":"#f5f5dc","bisque":"#ffe4c4","black":"#000000","blanchedalmond":"#ffebcd","blue":"#0000ff","blueviolet":"#8a2be2","brown":"#a52a2a","burlywood":"#deb887",
"cadetblue":"#5f9ea0","chartreuse":"#7fff00","chocolate":"#d2691e","coral":"#ff7f50","cornflowerblue":"#6495ed","cornsilk":"#fff8dc","crimson":"#dc143c","cyan":"#00ffff",
"darkblue":"#00008b","darkcyan":"#008b8b","darkgoldenrod":"#b8860b","darkgray":"#a9a9a9","darkgreen":"#006400","darkkhaki":"#bdb76b","darkmagenta":"#8b008b","darkolivegreen":"#556b2f",
"darkorange":"#ff8c00","darkorchid":"#9932cc","darkred":"#8b0000","darksalmon":"#e9967a","darkseagreen":"#8fbc8f","darkslateblue":"#483d8b","darkslategray":"#2f4f4f","darkturquoise":"#00ced1",
"darkviolet":"#9400d3","deeppink":"#ff1493","deepskyblue":"#00bfff","dimgray":"#696969","dodgerblue":"#1e90ff",
"firebrick":"#b22222","floralwhite":"#fffaf0","forestgreen":"#228b22","fuchsia":"#ff00ff",
"gainsboro":"#dcdcdc","ghostwhite":"#f8f8ff","gold":"#ffd700","goldenrod":"#daa520","gray":"#808080","green":"#04FD03","greenyellow":"#adff2f",
"honeydew":"#f0fff0","hotpink":"#ff69b4",
"indianred ":"#cd5c5c","indigo":"#4b0082","ivory":"#fffff0","khaki":"#f0e68c",
"lavender":"#e6e6fa","lavenderblush":"#fff0f5","lawngreen":"#7cfc00","lemonchiffon":"#fffacd","lightblue":"#add8e6","lightcoral":"#f08080","lightcyan":"#e0ffff","lightgoldenrodyellow":"#fafad2",
"lightgrey":"#d3d3d3","lightgreen":"#90ee90","lightpink":"#ffb6c1","lightsalmon":"#ffa07a","lightseagreen":"#20b2aa","lightskyblue":"#87cefa","lightslategray":"#778899","lightsteelblue":"#b0c4de",
"lightyellow":"#ffffe0","lime":"#00ff00","limegreen":"#32cd32","linen":"#faf0e6",
"magenta":"#ff00ff","maroon":"#800000","mediumaquamarine":"#66cdaa","mediumblue":"#0000cd","mediumorchid":"#ba55d3","mediumpurple":"#9370d8","mediumseagreen":"#3cb371","mediumslateblue":"#7b68ee",
"mediumspringgreen":"#00fa9a","mediumturquoise":"#48d1cc","mediumvioletred":"#c71585","midnightblue":"#191970","mintcream":"#f5fffa","mistyrose":"#ffe4e1","moccasin":"#ffe4b5",
"navajowhite":"#ffdead","navy":"#000080",
"oldlace":"#fdf5e6","olive":"#808000","olivedrab":"#6b8e23","orange":"#ffa500","orangered":"#ff4500","orchid":"#da70d6",
"palegoldenrod":"#eee8aa","palegreen":"#98fb98","paleturquoise":"#afeeee","palevioletred":"#d87093","papayawhip":"#ffefd5","peachpuff":"#ffdab9","peru":"#cd853f","pink":"#ffc0cb","plum":"#dda0dd","powderblue":"#b0e0e6","purple":"#800080",
"red":"#ff0000","rosybrown":"#bc8f8f","royalblue":"#4169e1",
"saddlebrown":"#8b4513","salmon":"#fa8072","sandybrown":"#f4a460", "darkbrown":"a7540c","seagreen":"#2e8b57","seashell":"#fff5ee","sienna":"#a0522d","silver":"#c0c0c0","skyblue":"#87ceeb","slateblue":"#6a5acd","slategray":"#708090","snow":"#fffafa","springgreen":"#00ff7f","steelblue":"#4682b4",
"tan":"#d2b48c","teal":"#008080","thistle":"#d8bfd8","tomato":"#ff6347","turquoise":"#40e0d0",
"violet":"#ee82ee",
"wheat":"#f5deb3","white":"#ffffff","whitesmoke":"#f5f5f5", "lightpurple":"#d1c3ef",
"yellow":"#ffff00","yellowgreen":"#9acd32",};


var labelFormat = ".3f";
var axisLabelFormat = ".2f";
var graphCommand = null;
var AXIS_SIGNIFICANT_DIGITS = 4;

var highChartBGColors = ['#FF00FF','#04FD03', "#FFFF00",'red',  'orange',  'white',  'silver', 'violet', 'deeppink', 'gray', 'beige', 'teal', '#C8C8C8'];
var highChartFGColorsMap = {"#04FD03": 'black',"white":'black', "beige":'black', "silver":'black', "#FFFF00":"black", "cyan":"black"};

var  dateTimeFormats =
{
  millisecond:"%b %e, %H:%M:%S.%L",
  second:"%b %e, %H:%M:%S",
  minute:"%b %e, %H:%M",
  hour:"%b %e, %H:%M",
  day:"%b %e, %Y",
  week:"Week from %b %e, %Y",
  month:"%B %Y",
  year:"%Y"
};

var  dataGroupingTimeFormats =
{
  millisecond: ['%b %e, %H:%M:%S.%L', '%b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'],
  second: ['%b %e, %H:%M:%S', '%b %e, %H:%M:%S', '-%H:%M:%S'],
  minute: ['%b %e, %H:%M', '%b %e, %H:%M', '-%H:%M'],
  hour: ['%b %e, %H:%M', '%b %e, %H:%M', '-%H:%M'],
  day: ['%b %e, %Y', '%b %e', '-%b %e, %Y'],
  week: ['Week from %b %e, %Y', '%b %e', '-%b %e, %Y'],
  month: ['%B %Y', '%B', '-%B %Y'],
  year: ['%Y', '%Y', '-%Y']
};

function getSeriesFGColor(bgcolor) {
		var c = highChartFGColorsMap[bgcolor];
		if (c == null) {
			return "#FFFFFF";
		} else {
			return colorsToHex[c];
		}
}

Highcharts.setOptions({
    global: {
      useUTC: true,
     /**
       * Use moment-timezone.js to return the timezone offset for individual
       * timestamps, used in the X axis labels and the tooltip header.
       */
      getTimezoneOffset: function (timestamp) {
          var offset =  moment( timestamp ).utcOffset();
          return -offset;
      }       
    },
	colors: highChartBGColors
});

Highcharts.Chart.prototype.getAllYAxes = function() {
    var axes = [];
    for ( index = 0; index < this.yAxis.length; index++ ) {
      var axis = this.yAxis[index];
      if ( axis.options.id != "navigator-y-axis" ) axes.push( axis );
    }

    return axes;
}

Highcharts.Chart.prototype.getAllXAxes = function() {
    var axes = [];
    for ( index = 0; index < this.xAxis.length; index++ ) {
      var axis = this.yAxis[index];
      if ( axis.options.id != "navigator-x-axis" ) axes.push( axis );
    }

    return axes;
}

Highcharts.Chart.prototype.resetYAxisExtremes = function( inRedraw ) {
    var axes = this.getAllYAxes();
    var index;
    var redraw = isDefined( inRedraw ) ? inRedraw : true;
    for ( index = 0; index < axes.length; index++ ) {
      this.setAxisExtremes( axes[index], null, null, redraw );
      

    }
}

Highcharts.Chart.prototype.getAllSeries = function( ) {
  var allSeries = [];
  for (var index = 0; index < this.series.length; index++) {
    var series = this.series[index];
    if (series.name != "Navigator" ) {
      allSeries.push(series);
    }
  }
  return allSeries;
}

Highcharts.Chart.prototype.getRenderedSeries = function( ) {
  var allSeries = [];
  var filtered = this.getAllSeries();
  for (var index = 0; index < filtered.length; index++) {
    var series = filtered[index];
    if (series.visible == true && series.linkedParent == null && series.name != "Navigator" ) {
      allSeries.push(series);
    }
  }
  return allSeries;
}




Highcharts.Chart.prototype.clearAllSeries = function ( ) {
  
    this.legend.resetTitle();  
    while (this.series.length > 0) {
      this.series[0].remove(false);
    }
    this.xAxis[0].setExtremes(null, null, true);

    this.yAxis.forEach(function(yAxis) {
      yAxis.setExtremes(null, null, true);
    });
    this.colorCounter = 0;
    this.symbolCounter = 0;
    
    this.zoomedTimes = [];    
    this.redraw();
}

Highcharts.Chart.prototype.setAxisExtremes = function ( axis, min, max, redraw ) {
  //should be an event emitted for yAxis update, this is a circular dependency
  if ( this.indicatorCache ) {
    this.indicatorCache.clearSeries( axis.series );
  }     

  axis.range = null;
  axis.update({
    "min": min,
    "max": max
  }, false);
  axis.setExtremes( min, max, redraw, redraw );
}

Highcharts.Chart.prototype.resetXAxisExtremes = function() {
    var axes = this.getAllXAxes();
    var index;
    for ( index = 0; index < axes.length; index++ ) {
      this.setAxisExtremes( axes[index], null, null, true );
    }
}


Highcharts.Chart.prototype.getOptionParameter = function ( key ) {
  if ( !this.options || ! this.options.mergedOptions  ) return null;
  var mergedOptions = this.options.mergedOptions;
  var returnValue = mergedOptions[ key ];
  var type = typeof returnValue;
  if ( type == "string" || type == "number" || type == "boolean") {
    if ( returnValue != null && type == "string" ) {
      returnValue = returnValue.toLowerCase().trim();
    }

    return returnValue;
  }
  return null;
}

Highcharts.Chart.prototype.getOptionParameterCaseInsensitive = function ( key ) {
  function findValue( mergedOptions, key ) {
    var lowerCaseKey = key.toLowerCase();
    var propertyNames = Object.getOwnPropertyNames( mergedOptions );
    if ( propertyNames == null || propertyNames.length == 0 ) {
      return null;
    }
    for ( var propertyIndex = 0; propertyIndex < propertyNames.length; propertyIndex++ ) {
      var propertyName = propertyNames[ propertyIndex ];
      if ( typeof propertyName != "string" ) {
        continue;
      }
      var lowerProperty = propertyName.toLowerCase();
      if ( lowerProperty == lowerCaseKey ) {
        var optionValue = mergedOptions[ propertyName ];
        if ( typeof optionValue == "undefined" ) {
          return null;
        }
        return optionValue;
      }
    }

    return null;
  }

  if ( !this.options || ! this.options.mergedOptions  ) return null;
  var mergedOptions = this.options.mergedOptions;
  var returnValue = mergedOptions[ key ];
  var type = typeof returnValue;
  if ( type == "string" || type == "number" || type == "boolean") {
    if ( returnValue != null && type == "string" ) {
      returnValue = returnValue.toLowerCase().trim();
    }

    return returnValue;
  }
  return null;
}

Highcharts.Chart.prototype.getOptionParameterBoolean = function ( key, defaultValue ) {
  var value = this.getOptionParameter( key );
  if ( value == null ) {
    return defaultValue;
  }
  return this._isBoolean( value );
}

Highcharts.Chart.prototype._isBoolean = function ( value ) {
  return value == true || value == 'y' || value == 'yes' || value == 'true';
}

/**
 * Set a parameter programmatically, can be override from excel
 * @param key
 * @param value
 * @returns {null}
 */
Highcharts.Chart.prototype.setOptionParameter = function ( key, value ) {
  if ( !this.options || ! this.options.mergedOptions  ) return null;
  var mergedOptions = this.options.mergedOptions;
  if ( isDefined( mergedOptions ) ) {
    mergedOptions[ key ] = value;
  }
}

Highcharts.Chart.prototype.setOptionXAxisZoom = function () {
  var offset = this.getOptionParameter(KEY_STATIC_START_OFFSET);
  if ( offset ) {
    var original = Date.now();
    var startingTime = new Date( original );
    manipulateDate( startingTime, offset );
    if ( original == startingTime.valueOf() ) {
      //offset didnt do anything, should be logged in the manipulate Call
      return;
    }
    console.info( "Setting left most X-Axis to the point closest to ", startingTime );

    this.xAxis[0].update( { min: startingTime.getTime() }, false);
    this.xAxis[0].setExtremes( startingTime.getTime(), null, true );

  }
}


Highcharts.Chart.prototype.hashDate = function( dateObject ) {
  return dateObject.getHours() * 60 * 60 * 1000 + dateObject.getMinutes() * 60 * 1000 + dateObject.getSeconds() * 1000 + dateObject.getMilliseconds();
}


Highcharts.Chart.prototype.isWithinBusinessHours = function( millisecondTime ) {
  var dateObject = new Date( millisecondTime );
  var businessHours = this.options.mergedOptions.businessHours;
  var index;
  var range;
  var afterStart;
  var beforeEnd;
  if ( !businessHours || businessHours.length == 0 ) return true;
  var dateHash = this.hashDate( dateObject );


  for ( index = 0; index < businessHours.length; index++ ) {
    range = businessHours[ index ];


    afterStart = this.hashDate( range.start ) <= dateHash;
    //no need to check the end if we are not in range
    if ( !afterStart ) continue;

    beforeEnd = this.hashDate( range.end ) >= dateHash;

    if ( afterStart && beforeEnd ) return true;
  }

  return false;
}


Highcharts.Chart.prototype.isManuallyZoomed = function(  ) {
  if ( isNotValued( this.options.manuallyZoomed ) ) {
    return false;
  }

  return this.options.manuallyZoomed;
}

Highcharts.Chart.prototype.setLiveWindow = function( milliseconds, label ) {
  var xAxis = this.xAxis[0];

  this.options.liveWindow = milliseconds;
  this.options.liveWindowLabel = label;
  if ( milliseconds == null ) {
    console.info( "XAxis range set to ALL");  
  } else {
    console.info( "XAxis range set to ", milliseconds, "ms");  
  }
  this.recalculateTitle();
}

/**
 * Meant to be user based so redraw after request
 * @param milliseconds
 * @param label
 */
Highcharts.Chart.prototype.changeLiveWindow = function( milliseconds, label ) {
  this.clearManualZoom();
  this.setLiveWindow( milliseconds, label );
  this.moveLiveWindow();
  if ( milliseconds == null ) {
    this.zoomOut();
  }
  
  this.queuePercentile();
  this.redraw();
}

Highcharts.Chart.prototype.getPercentile = function() {
  return this.percentile;
}

/**
 * Current live window value, null if never set as we'll be in full mode
 */
Highcharts.Chart.prototype.getLiveWindow = function( ) {
  var xAxis = this.xAxis[0];
  var value = this.options.liveWindow;
  if ( !isDefined( value )) {
    value = null;
  }
  return value;
}

Highcharts.Chart.prototype.getBusinessHoour = function( ) {
  return this.getOptionParameterCaseInsensitive( KEY_BUSINESS_HOURS );
}

Highcharts.Chart.prototype.getBusinessHours = function( ) {
  return this.getOptionParameterCaseInsensitive( KEY_BUSINESS_HOURS );
}


Highcharts.Chart.prototype.getOHLCStart = function( ) {
  return this.getOptionParameterCaseInsensitive( KEY_OHLC_START );
}

Highcharts.Chart.prototype.getOHLCPeriod = function( ) {
  return this.getOptionParameterCaseInsensitive( KEY_OHLC_PERIOD );
}

Highcharts.Chart.prototype.getOHLCChartType = function( ) {
 return this.getOptionParameterCaseInsensitive( KEY_OHLC_CHART_TYPE );  
}

Highcharts.Chart.prototype.getOHLCRelaxedGrouping = function( ) {
  return this._isBoolean( this.getOptionParameterCaseInsensitive( KEY_OHLC_DATAGROUPING_RELAXED ) );  
}

Highcharts.Chart.prototype.setOHLCChartType = function( newChartType ) {
  if ( newChartType != SERIES_TYPE_OHLC && newChartType != SERIES_TYPE_CANDLESTICK ) {
    return;
  }
  this.setOptionParameter( KEY_OHLC_CHART_TYPE, newChartType  );

  this.series.forEach( function ( inSeries ) {
    if ( inSeries.linkedParent != null && inSeries.type != newChartType ) {
      inSeries.update( { "type" : newChartType }, true );
    }
  });

 
}


Highcharts.Chart.prototype.setOHLCRelaxedGrouping = function( mode ) {
  this.setOptionParameter( KEY_OHLC_DATAGROUPING_RELAXED, mode  );
  this.series[0].update( { "visible" : true}, true );

  this.redraw();
}


Highcharts.Chart.prototype.setArrayExtremes = function( extremes, array ) {
  if ( isDefined ( array ) && array != null ) {
    for ( var index = 0; index < array.length; index++ ) {
      var value = array[ index ];
      if ( extremes.min == null || value < extremes.min  ) extremes.min = value;
      if ( extremes.max == null || value > extremes.max  ) extremes.max = value;
    }
  }
}

Highcharts.Chart.prototype.getAxisExtremes = function ( axis, dataArrayField ) {
  var extremes = { min: null, max: null };

  if ( isDefined( axis ) && axis != null ) {
    for ( var index = 0; index < axis.series.length; index++ ) {
      var series = axis.series[ index ];
      this.setArrayExtremes( extremes, series[ dataArrayField ]);
    }
  }

  return extremes;
}


Highcharts.Chart.prototype.getSeriesByName = function ( seriesName ) {
	if ( isValued( this.series ) == false || this.series.length == 0 ) {
		return null;
	}
  for ( var index =0 ; index < this.series.length; index++ ) {
    var currentSeries = this.series[ index ];
    if ( currentSeries.name == seriesName ) {
      return currentSeries;
    }
  }
  return null;  
}

Highcharts.Chart.prototype.STEP_MODES = { "NONE" : 0, "LEFT": 1, "CENTER": 2, "RIGHT" : 3 };


Highcharts.Chart.prototype.setSeriesStep = function ( seriesName, stepMode ) {
  var chart = this;
  var series = chart.getSeriesByName( seriesName );
  if ( series == null ) {
    return;
  }
  var currentStepMode = series.options.step;
  var requestedStepMode = null;
  switch( stepMode ) {
    case chart.STEP_MODES.NONE : 
      requestedStepMode = null;
      break;
    case chart.STEP_MODES.LEFT :       
      requestedStepMode = "left";
      break;
    case chart.STEP_MODES.CENTER :       
      requestedStepMode = "center";
      break;
    case chart.STEP_MODES.RIGHT :       
      requestedStepMode = "right";
      break;
    default:
      console.log( "Unsupported step Mode:", stepMode)
      return;
  }
  if ( currentStepMode == requestedStepMode ) {
    return;
  }
  series.update( { step: requestedStepMode }, true );

}

Highcharts.Chart.prototype.changeFontSize = function( absolute ) {
  var currentSize = this.getCurrentFontSize();
  if ( isNaN( currentSize ) ) {
    console.error( "Can't use font size function, the containing element of the chart does not have a size attached to it or is not in pixels")
    return;
  }
  console.info( "Changing font size to: ", absolute );
  this.container.parentElement.style.fontSize = ( absolute ) + "px";

  this.setOptionParameter( KEY_CHART_FONT_SIZE, absolute );
  
}

Highcharts.Chart.prototype.getCurrentFontSize = function(){
  var size = this.container.parentElement.style.fontSize;
  return Number( size.replace( "px", "" ) );
}

/**
 * Locate the x point closest but greater than the target point 
 * TODO use a modified binary search to get this to o(log(n)) instead of o(n)
 */
Highcharts.Axis.prototype.findNextXPoint = function( targetPoint ) {
  if ( !isDefined( this.series ) || this.series.length == 0 ) return targetPoint;
  var nextPoint = null;

  //bail the loop if target point matches exactly
  for ( var seriesIndex = 0; seriesIndex < this.series.length && nextPoint != targetPoint; seriesIndex++ ) {
    var inSeries = this.series[ seriesIndex ];
    for ( var seriesDataIndex = 0; seriesDataIndex < inSeries.xData.length; seriesDataIndex++ ) {
      var xPoint = inSeries.xData[ seriesDataIndex ];
      if ( xPoint >= targetPoint ) {
        if ( nextPoint == null || ( xPoint < nextPoint ) ) {
          nextPoint = xPoint;
        }
        break;
      }
    }

  }

  return nextPoint == null ? targetPoint : nextPoint ;
}

/**
 * The timestamp to start rolling
 */
Highcharts.Chart.prototype.getWindowTime = function() {
  return Date.now();
}

Highcharts.Chart.prototype.moveLiveWindow = function() {
  //dont move when zoomed
  if ( this.isManuallyZoomed() ) return;
  var chart = this;
  var xAxis = this.xAxis[0];
  var extremes = xAxis.getExtremes();
  var dataExtremes = this.getAxisExtremes( xAxis, "xData" ); 
  var newMin = extremes.userMin;
  var newMax = extremes.userMax;
  var liveWindow = this.getLiveWindow();


  if ( isDefined( liveWindow ) ) {
    //requested full mode
    if ( liveWindow == null ) {
      //already showing you everything
      if ( !isDefined( extremes.userMin ) && !isDefined( extremes.userMax ) ) return;

      newMin = null;
    } else {
      newMin = this.getWindowTime() - liveWindow;

      //get everything again..thats what null means
      if ( newMin < dataExtremes.min ) newMin = null;
      else {
        var closestDataPoint = xAxis.findNextXPoint( newMin );
        if ( closestDataPoint != newMin ) {
          logger.info( "Found an actual data point within view, changing from ", newMin, " to ", closestDataPoint );
          newMin = closestDataPoint;
        }
      }
    }
  }
  if ( isNaN( newMin ) ) {
    newMin = null;
  }
  //we need to show up to date values...clear the max
  if ( newMax <= dataExtremes.max ) newMax = null;
  if ( newMin != extremes.userMin || newMax != extremes.userMax ) {
    this.setAxisExtremes( xAxis, newMin, newMax, false);
  }
}


Highcharts.Chart.prototype.clearManualZoom = function(  ) {
  this.options.manuallyZoomed = null;
}

/**
 * Print as much state as possible so the user knows whats going on
 */
Highcharts.Chart.prototype.recalculateTitle = function() {
  var labels = [];
  var liveWindowLabel = this.options.liveWindowLabel;
  if ( this.isManuallyZoomed() ) {
    labels.push( "Manually Zoomed" );
  } else if ( isDefined( liveWindowLabel ) ) {
    labels.push( liveWindowLabel );
  }
  this.setTitle( { text: getChartTitle( getConfig(), labels) }, null, false );
}

/**
 * Check to see if we have enough data in the series to enable data grouping
 */
Highcharts.Chart.prototype.checkDataGrouping = function() {
 return false;
}

/**
 * Recalculate/refresh due to parameter/data changes that can/need to be done
 * after every refresh.
 *
 * @method     recalculateParameters
 */
Highcharts.Chart.prototype.recalculateParameters = function( previousOptions ) {
  var changed = false;
  var axisType = this.getOptionParameter( KEY_AXIS_TYPE );
  if (  STRING_UTILS.isNotEqual( axisType, this.axisType ) ) {
    logger.info ( "Axis Type changed from ", this.axisType, " to ", axisType );
    this.setAxisType( axisType );
    changed = true;
  }
  this.recalculateParametersDataGrouping( previousOptions );
  this.recalculateFontSize();


  return changed;
}

Highcharts.Chart.prototype.recalculateParametersDataGrouping = function( previousOptions ) {
  var enabled = this.getOptionParameterBoolean( KEY_DATAGROUPING_ENABLED );
  this.series.forEach( function( s ) {
    var dataGrouping = s.options.dataGrouping;
    if ( isNotValued( dataGrouping ) ) {
      return;
    }
    var existingStatus = dataGrouping.enabled;
    if ( isValued( existingStatus ) && existingStatus === enabled ) {
      return;
    }
    logger.info( "Series Datagrouping changed to: ", enabled );

    s.update( { 
      "dataGrouping.enabled": enabled
    });
  });

}

Highcharts.Chart.prototype.recalculateFontSize = function() {
  var fontSize = this.getOptionParameterCaseInsensitive( KEY_CHART_FONT_SIZE );
  if ( typeof fontSize == "undefined" ) {
    return;
  }

  var parsed = parseInt( fontSize );
  if ( isNaN( parsed ) ) {
    return;
  }
  this.changeFontSize( parsed );
}

Highcharts.Chart.prototype.isConnected = function() {
  return !isDefined( this.connected ) ? true : this.connected;
}

Highcharts.Chart.prototype.getConnectionStatus = function(){
  return !isDefined( this.connectionStatus ) ? "connected" : this.connectionStatus;
}

Highcharts.Chart.prototype.setConnectionStatus = function( connectionStatus ) {
  if ( this.connectionStatus != connectionStatus ) {
	if( this.imageConnected != null ){
		this.imageConnected.destroy();
		this.imageConnected = null;
	}
  }
  this.connectionStatus = connectionStatus;
  connectionStatus == "connected" ? this.connected = true : this.connected = false;
}

Highcharts.Chart.prototype.showConnectionStatus = function() {
  var iconSize = 20;
  var expectedY = this.containerHeight - iconSize - 5;
  var iconFile = this.isConnected() ?  "./img/FlatConnect.png" : "./img/FlatDisconnect.png";

  if ( !this.isConnected() ) {
	if (this.getConnectionStatus() == "partialDisconnect"){
		iconFile = "./img/FlatPartialDisconnect.png";
	}
  }

  if ( this.imageConnected ) {
    //if ( this.imageConnected)
    var y = parseFloat (this.imageConnected.element.getAttribute( "y" ) );
    if ( y != expectedY ) {
      this.imageConnected.element.setAttribute( "y", expectedY );
    }
    this.imageConnected.show();
  }  else {
    this.imageConnected = this.renderer.image( iconFile, 5, expectedY, iconSize, iconSize );
    //if ( !this.isConnected() ) {
      //this.imageConnected.element.setAttribute( "class", "animate-flicker");
    //}
	var title = document.createElementNS("http://www.w3.org/2000/svg", "title");
	this.imageConnected.element.appendChild(title);
	this.imageConnected.element.addEventListener("click", function (event) {
		if( this.isConnected() ){
      //TODO this is a cyclic dependency..going out to maincharting from here
      var socketURL = isValued( chartSocket ) ? chartSocket.url : "[testing conn]" ;

			title.textContent = "Connected to " + socketURL  + "\n" +
        "Message count " + messageCount + "\n";
			if ( this.series ) {
				var seriesData = "";
				for ( var index = 0; index < this.series.length; index++ ) {
					seriesData = seriesData.concat( this.series[index].name, ":", this.series[index].xData.length );
					if( index < this.series.length - 1 ){
						seriesData = seriesData.concat("\n");
					}
				}
			}
			title.textContent = title.textContent.concat(seriesData);
      var pilotVersionDate = this.options.pilotVersion;
      if ( isValued( pilotVersionDate ) ) {
        title.textContent = title.textContent.concat( '\nGraph Version: ' ).concat( pilotVersionDate.toLocaleString( "en-GB") );
      }

		}else if ( this.getConnectionStatus() == "partialDisconnect" ) {
			var d = new Date();
			title.textContent = "Disconnected from Excel at " + d.toTimeString();
		} else{
			var d = new Date();
			title.textContent = "Disconnected from ChartingViewServer at " + d.toTimeString();
		}
	}.bind(this));
	this.imageConnected.add();
  }
}

Highcharts.Chart.prototype.setSeriesVisibility = function( visible ) {
  this.series.forEach( function( s ) {
    if ( visible ) {
      s.show();
    } else {
      s.hide();
    }
  });

}

Highcharts.Chart.prototype.removeUnusedYAxis = function() {
  var removedAxis = [];
  this.yAxis.forEach( function( axis ) {
    //dont delete the default axis
    if ( axis.options.id == "yDefaultAxis" ) return;
    if ( axis.series == null || axis.series.length == 0  ) {
      removedAxis.push( axis );
    }
  });

  removedAxis.forEach(function( axis ) {
    axis.remove( true );
  });
}


Highcharts.Chart.prototype.getAxisColorOptions = function( axisColor ) {
  return {
    lineColor: axisColor,
    tickColor: axisColor,
    labels: {
      style: {
        color: axisColor
      }
    }
  };
}
/**
 * UI function, update and paint NOW
 * @param axisType
 */
Highcharts.Chart.prototype.setAxisType = function( axisType ) {
  console.info( "Requesting axis type: ", axisType );
  var chart = this;
  this.axisType = axisType;
  switch ( STRING_UTILS.toLowerCase( axisType ) ) {
    case STRING_UTILS.toLowerCase( AXIS_TYPE_INTELLIGENT ):
      this.intelligentAxis.processSeries();
      var setFunction = this.setAxisType.bind( this, axisType );
      if ( !this._intelligentTimer ) {
        var interval = this.getOptionParameter( KEY_AXIS_INTELLIGENT_INTERVAL );
        var timerInterval = interval * 1000;
        logger.info ( "Intelligent axis timer set for ", interval, "seconds" );

        this._intelligentTimer = window.setInterval( setFunction, interval * 1000 );
        this._intelligentInterval = interval;
      } else {
        var currentInterval = this.getOptionParameter( KEY_AXIS_INTELLIGENT_INTERVAL );
        if ( currentInterval != this._intelligentInterval ) {
          clearInterval( this._intelligentTimer );
          this._intelligentTimer = null;
          //prep the next call..
          window.setTimeout( setFunction, currentInterval );
        }
      }

      break;
    case ( STRING_UTILS.toLowerCase( AXIS_TYPE_OWN ) ):
      var processedSeriesCount = 0;
      this.series.forEach( function( inSeries ) {
        if ( inSeries.linkedParent != null ) {
          inSeries.update( { yAxis: inSeries.linkedParent.yAxis.options.id }, true);
          return;
        }
        if ( inSeries.isDerivedSeries() ) {
          inSeries.update( { yAxis: 0 }, true);
          return;
        }

        while( processedSeriesCount + 1 > this.yAxis.length ) {
          assignYAxis( "axis-"+(this.yAxis.length).toString(), { left: ( this.yAxis.length ) % 2 == 0 });
        }
        if ( inSeries.yAxis.options.index != processedSeriesCount ) {
          inSeries.update( { yAxis: processedSeriesCount }, true);
        }
        processedSeriesCount++;
      }.bind( this ));
      break;
    case STRING_UTILS.toLowerCase( AXIS_TYPE_SHARED ):
      this.series.forEach( function( inSeries ) {
        if ( inSeries.yAxis.options.index != 0 ) {
          this.yAxis[ 0 ].update( this.getAxisColorOptions( defaultAxisColor ), true);
          inSeries.update( { yAxis: 0 }, true);
        }
      }.bind( this ));

      break;
  };
  if ( AXIS_TYPE_INTELLIGENT != axisType && isValued( this._intelligentTimer ) ) {
    logger.info ( "Cleared intelligent axis timer" );
    window.clearInterval( this._intelligentTimer );
  }

  this.removeUnusedYAxis();
  this.colorYAxis();
  this.getPercentile().zoomToPercentile();
  this.setOptionParameter( KEY_AXIS_TYPE, axisType );
  
}

Highcharts.Chart.prototype.colorYAxis = function() {
  this.yAxis.forEach( function( axis ) {
    var axisColor = null;
    axis.series.forEach( function( axisSeries ) { 
      if ( axisSeries.isDerivedSeries( ) ) {
        return;
      }
      if ( axisColor == null ) {
        axisColor = axisSeries.color;
      }
      if ( axisColor != axisSeries.color ) {
        axisColor = defaultAxisColor;
      }
    });
    if ( axisColor == null ) {
      axisColor = defaultAxisColor;
    }
    axis.update( this.getAxisColorOptions( axisColor ), true);
  }.bind( this ) );
}

Highcharts.Chart.prototype.countYAxis = function( left ) {
  var axes = this.getAllYAxes();
  if ( !isDefined( axes )) return 0;
  var counter = 0;
  axes.forEach( function( axis ) {
    if ( axis.opposite == !left ) counter++;
  });

  return counter;
}


Highcharts.Chart.prototype.onHistoricalEnd = function() {
  var chart = this;
  function tagHistoricalTimes() {
    var earliestEndTime = Number.MAX_SAFE_INTEGER;;
    chart.getAllSeries().forEach( function( series ) {
      if ( series.xData && series.xData.length > 0 ) {
        var lastX =  series.xData[ series.xData.length - 1 ] ;
        if ( lastX < earliestEndTime ) {
          earliestEndTime = lastX;
        }
      }
    });
    if ( earliestEndTime != Number.MAX_SAFE_INTEGER ) {
      chart.setHistoricalEndTime( earliestEndTime );
    }
    console.log( "Historical End Time", 
      ( chart.getHistoricalEndTime() ) ?  
        new Date( new Date( chart.getHistoricalEndTime() ) )  + " [" +( chart.getHistoricalEndTime() % 1000 ) + "]ms": "N/A"
    );
    
  }

  
  chart.series.forEach(function (s) {
    var start = new Date().getTime();
    if (!isValued(s._data)) {
      s._data = [];
    }
    s._data.sort(function (a, b) {
      var ax = a[0], bx=b[0];
      if (ax > bx) {
          return 1;
      }
      if (ax < bx) {
          return -1;
      }
      return 0;
    });
    console.log("Sort time: " + (new Date().getTime() - start))
    s.setData(s._data, false );
  }, this);

  chart.setSeriesVisibility(true);
  chart.options.legend.enabled = true;
  chart.legend.display = true;
  chart.onHistoricalEndPostSort();
  chart.getPercentile().zoomToPercentile();
  chart.hideLoading();
  tagHistoricalTimes()
}

Highcharts.Chart.prototype.onHistoricalEndPostSort = function() {

}

Highcharts.Chart.prototype.getContextMenu = function( menuConfig ) {
  return  this.menuGenerator.generateMenu( menuConfig );
}

Highcharts.Chart.prototype.isSeriesCoordinate = function( x, y ) {
  if ( typeof x == "undefined" || typeof y == "undefined" ) {
    return false;
  }

  var options = {
		"chart" : this,
		"tooltip" : {
			"enabled" : false
		}
	}  
  var pointer = this.pointer;
  var elementsPointed = document.elementsFromPoint( x, y );
  if ( elementsPointed == null || elementsPointed.length == 0 ){
    return false;
  }
  console.log( "x/y",x,y, elementsPointed );
  for ( var index = 0; index < elementsPointed.length; index ++ ) {
    var elementPointed = elementsPointed[ index ];
    if ( pointer.inClass(elementPointed, 'highcharts-tracker') ) {
      return true;
    }
  }

  return false;  
}

Highcharts.Chart.prototype.setSampled = function( sampled ) {
  console.log( "data is sampled, will attempt to ask for unsampled data when points are no longer grouped during a zoom in.");
  this.sampled = sampled;
}

Highcharts.Chart.prototype.isSampled = function( ) {
  return typeof this.sampled == 'boolean' ? this.sampled : false;
}

Highcharts.Chart.prototype.addChartPublisher = function( listener ) {
  if ( ! listener ) {
    return;
  }
  this.publishListeners.push( listener );
}

Highcharts.Chart.prototype.handleManualZoomEvent = function() {
  if ( this.isSampled() == false ) {
    return;
  }
  var chart = this;

  function isAlreadyZoomed( start, end ) {
    if ( chart.zoomedTimes.length == 0 ) {
      return false;
    }
    for ( var index = 0; index < chart.zoomedTimes.length; index++ ) {
      var currentTimes = chart.zoomedTimes[ index ];
      var previousStart =  currentTimes[ 0 ];
      var previousEnd =  currentTimes[ 1 ];
      if ( start >= previousStart && end <= previousEnd ) {
        return true;
      }
    }
    return false;
  }
  var bounds = chart.xAxis[ 0 ].getExtremes();
  var start = bounds.userMin;
  var end = bounds.userMax;
  if ( typeof start == "undefined") {
    start = bounds.min;
  }
  if ( typeof end == "undefined") {
    end = bounds.max;
  }

  if ( isAlreadyZoomed( start, end ) ) {
    return;
  }
  var historicalEndTime = chart.getHistoricalEndTime();
  if( historicalEndTime != null && start >= historicalEndTime ) {
    return;
  }
  if ( historicalEndTime != null && end > historicalEndTime ) {
    console.log( "Capping zoom time to the historical time: ", historicalEndTime );
    end = historicalEndTime;
  }

  var ungrouped = false;
  chart.getAllSeries().forEach(function( series ) {

    var seriesGrouped = false
    if ( typeof series.hasGroupedData != "undefined" ) {
      seriesGrouped = series.hasGroupedData;
    }
    ungrouped = ungrouped || seriesGrouped == false;
  });
  if ( ungrouped == false ) {
    return;
  }
  if ( !this.publishListeners ) {
    console.log( "Need a external publish listener configured to re-request the data for sampled data when grouping is disabled." );
    return;
  }
  
 
  chart.zoomedTimes.push( [ Math.floor( start ), Math.ceil( end ) ] );

  console.log( "Sampled Query Data -> Requesting zoomed data from " + new Date( start) + " to " + new Date( end ) );

  var request = {
    "appid": chart.options.pilotConfig.appid,
    "chartName": chart.options.pilotConfig.chartName,
    "type" : "command",
    "command" : "zoomRequest",
    "zoomStart" : start,
    "zoomEnd" : end
  };
  this.publishListeners.forEach( function( listener ) {
    listener.call( listener, JSON.stringify( request ) );
  });

}

Highcharts.Chart.prototype.processZoomedData = function( seriesUpdate ) {
  var chart = this;
  var zoomStart = seriesUpdate.zoomStart;
  var zoomEnd = seriesUpdate.zoomEnd;

  var series  = this.getSeriesByName( seriesUpdate.seriesName );
  if ( series == null ) {
    console.log( "can't find series: ", seriesUpdate.seriesName );
    return;
  }
  var data = series.options.data;
  var newData = [];
  var pushed = false;
  var start = Date.now();
  data.forEach( function ( existingPoint ) {
    var x = existingPoint[0];
    if ( x < zoomStart || x > zoomEnd ) {
      newData.push( existingPoint );
    } else if ( pushed == false ) {
      var convertedData = chart.convertData( series, seriesUpdate );

      Array.prototype.push.apply( newData, convertedData );
      pushed = true;
    }
  });

  series.setData( newData, false, false, false );
  console.log( "Point(s) ", seriesUpdate.data.length,  "  took: ", (Date.now() - start ), "ms" );

  chart.queuePercentile();
  chart.redraw( false );


  
}

Highcharts.Chart.prototype.queuePercentile = function() {
  var localChart = this;
  console.log( "Queueing a percentile refresh after redraw" );
  localChart.redrawQueue.push( function() {
    localChart.getPercentile().zoomToPercentile();
  });

}

Highcharts.Chart.prototype.getHistoricalEndTime = function () {
  if ( this.options.historicalEndTime ) {
    return this.options.historicalEndTime;
  } else {
    return null;
  }
}

Highcharts.Chart.prototype.setHistoricalEndTime = function ( time ) {
  this.options.historicalEndTime = time;
}

Highcharts.Chart.prototype.convertData = function ( targetSeries, seriesData ) {
  var chart = this;

  function convertOHLCData( seriesData ) {
    var convertedData = [];
    for (var index = 0; index < seriesData.data.length; ++index) {
      var point = seriesData.data[index];
      convertedData.push([
        point.x,
        point.open,
        point.high,
        point.low,
        point.close
      ]);
    }
    return convertedData;
  }

  function convertLineData(seriesData) {
    var convertedData = [];
    var addPoint;
    for (var index = 0; index < seriesData.data.length; index++) {
      addPoint = true;
      var point = seriesData.data[index];
      if ( isNaN( point.x ) ) {
        console.log("Invalid point x value: ", point);
        continue;
      }

      //null points are hidden
      if ( "y" in point == false ) {
          point.y = null;
      } else if ( isNaN(point.y)) {
        console.log("Invalid point y value: ", point);
        continue;
      }
      var xValue;
      if (seriesData.fixedXValue && seriesData.fixedXValue != null) {
        xValue = seriesData.fixedXValue;
      } else {
        xValue = point.x;
      }

      if (seriesData.timeSeries && !chart.isWithinBusinessHours(xValue)) {
        addPoint = false;
      }
      if (addPoint) {
        var pointAsArray = [xValue, point.y];
        convertedData.push(pointAsArray);
      }

    }
    return convertedData;
  }
  
  function convertBidOfferSpread(seriesData) {
    var spreadData = [];
    var midData = [];
    for (var index = 0; index < seriesData.data.length; ++index) {
      var point = seriesData.data[index];
      spreadData.push([
        point.x,
        point.offer,
        point.bid,
      ]);
      midData.push([
        point.x,
        point.bid + (point.offer - point.bid) / 2,
      ]);

    }
    var name = seriesData.seriesName + "-MID"
    for (var index = 0; index < highCharts.series.length; index++) {
      if (highCharts.series[index].name == name) {
        for (var i = 0; i < midData.length; i++) {
          highCharts.series[index].addPoint(midData[i], false, false, false);
        }
      }
    }
    return spreadData;
  }

 if (seriesData.data && seriesData.data.length > 0) {
    if (targetSeries.type == "ohlc" || targetSeries.type == "candlestick" ) {
      return convertOHLCData(seriesData);
    } else if (seriesData.data[0].bid) {
      return convertBidOfferSpread(seriesData);
    } else {
      return convertLineData(seriesData);
    }
  } else {
    return [];
  }  
}


/**
 * Need to apply pilot bits so that the last grouped value is the last value
 * if we are in a floating window
 */
Highcharts.wrap(Highcharts.Series.prototype, "groupData", function(proceed) {
  var groupedValues = proceed.apply(this, Array.prototype.slice.call(arguments, 1));

  if ( groupedValues != null && ! this.chart.isManuallyZoomed() ) {
    var xValues = groupedValues[0];
    var yValues = groupedValues[1];
    var showPoints = this.chart.getOptionParameter( KEY_GROUP_SHOW_LAST_POINTS );

    var index = 0;
    xValues.pop();
    yValues.pop();
    for ( var index = 0; index < showPoints; index++ ) {
      yValues.push( this.yData[ this.yData.length - showPoints + index ] );
      xValues.push( this.xData[ this.xData.length - showPoints + index ] );
    }

  }

  return groupedValues;
});


/**
 * When calculating for data grouping, make the connected lines respect the original grouped width (less datagrouping)
 */

Highcharts.wrap(Highcharts.Series.prototype, "processData", function( proceed ) {
  if ( this.chart.getOptionParameterBoolean( KEY_OHLC_DATAGROUPING_RELAXED ) == false ) {
    return proceed.apply( this, Array.prototype.slice.call(arguments, 1) );
  }

  var series = this;
  var xAxis = series.xAxis;


  if ( series.options.dataGrouping.enabled != true && series.options.dataGrouping.enabled != "Yes" ) {
    return proceed.apply( this, Array.prototype.slice.call(arguments, 1) );
  }

  if ( !series.linkedSeries || series.linkedSeries.length == 0 || series.options.type != "line" ) {
    return proceed.apply( this, Array.prototype.slice.call(arguments, 1) );
  }
  
  var originalPixelWidthFunction = xAxis.getGroupPixelWidth;
  var requestedWidth = series.options.dataGrouping.groupPixelWidth;
  if ( typeof requestedWidth == "undefined" || xAxis.getGroupPixelWidth() != requestedWidth ) {
    xAxis.getGroupPixelWidth = function() {
      return requestedWidth;
    }
    logger.info( "ohlc relaxed grouping width of ", requestedWidth, " being applied instead of " , xAxis.getGroupPixelWidth() );
  }
  
  proceed.apply( this, Array.prototype.slice.call(arguments, 1) );

  xAxis.getGroupPixelWidth = originalPixelWidthFunction;
});


Highcharts.Series.prototype.getXPoint = function( x ) {
  if ( !isValued( x ) || !isValued( this.points ) || this.points.length == 0 ) return null;
  for( var index = 0; this.points.length; index++ ) {
    if ( this.points[index].x == x ) return this.points[index];
  }

  return null;
};

Highcharts.Series.prototype.getYAxisIndex = function() {
  if ( typeof this.yAxis == "object" ) {
    return this.yAxis.options.index;
  }
  return 0;
}

Highcharts.Series.prototype.getPointsWithinX = function (series, xMin, xMax) {
  var data = [];
  //nulls mean everything
  if (!isDefined(xMin) || !isDefined(xMax) || xMin == null || xMax == null) return series.yData;
  if (!isDefined(series) || series == null || !isDefined(series.xData) || series.xData == null) return data;
  for (var innerIndex = 0; innerIndex < series.xData.length; innerIndex++) {
    var xValue = series.xData[innerIndex];
    var yValue = series.yData[innerIndex];
    if (xValue != null && xValue >= xMin && xValue <= xMax && yValue != null) data.push( yValue );
  }

  return data;
}

Highcharts.Series.prototype.isDerivedSeries = function () {
  return this.linkedParent != null || this.options.errorMessage;
}




Highcharts.Axis.prototype.getSignificantDigits = function () {
  var significantDigits = AXIS_SIGNIFICANT_DIGITS;
  for ( significantDigits; significantDigits < 10; significantDigits++ ) {
    var previousValue = null;
    for ( var tickIndex = 0; tickIndex < this.tickPositions.length; tickIndex++ ) {
      currentValue = NUMBER_UTILS.formatSignificant( this.tickPositions[ tickIndex ], significantDigits  );

      //duplicate...start over with new significant digit
      if ( previousValue != null && currentValue == previousValue ) break;
      previousValue = currentValue;
    }
    //went through whole list with not previous dups...these are sorted
    //there may be a use case where the duplicate is not the previous? can't think of it
    if ( tickIndex == this.tickPositions.length ) break;
  }

  return significantDigits;
}

 Highcharts.Legend.prototype.getHoverPoints = function () {
    var chart = this.chart;
    var hoverPoints = chart.hoverPoints;

    if (!hoverPoints && chart.hoverPoint) {
      hoverPoints = [chart.hoverPoint];
    }
    return hoverPoints;
  }


Highcharts.Legend.prototype.setDirty = function( dirty ) {
  this.chart.isDirtyLegend = dirty;
  this.chart.isDirtyBox = dirty;
}


Highcharts.Legend.prototype.reposition = function( x, y ) {
  var chart = this.chart;
  if ( !chart ) return;
  if ( !chart.plotBackground  ) return;
  if ( !chart.options.legend || chart.options.legend.enabled == false ) return;
  if ( chart.series.length == 0 ) return;
  var changedSize = this.cachedLeft != chart.plotLeft || this.cachedHeight != chart.plotHeight;
  if ( !changedSize && isValued( this.moved ) && this.moved == true ) return;
  var newX = chart.plotLeft;
  var newY = chart.plotHeight - this.box.getBBox().height;
  this.options.x = newX;
  this.options.y = newY;
  this.group.translate( newX, newY );

  this.setDirty( true );
  this.moved = true;
  this.cachedLeft = chart.plotLeft;
  this.cachedHeight = chart.plotHeight;
  this.render();
}


Highcharts.Legend.prototype.onHistoricalStart = function() {

}  

Highcharts.wrap( Highcharts.Legend.prototype, "renderItem", function( fn, item ) {
  fn.call( this, item );
  var groupElement = item.legendGroup.element;
  var legendItemElement = item.legendItem.element;
  if ( legendItemElement.children.length < 2 ) {
    //not ready with formatted values
    return;
  }
  var seriesNameElement = legendItemElement.firstChild;
  var valueElement = legendItemElement.children[1];
  var legendRectangleItemId = "legend-item-rect-" + seriesNameElement.textContent.slice( 0, -3 );
  var rect = document.getElementById( legendRectangleItemId );

  if ( seriesNameElement.style.fill == valueElement.style.fill ) { //same color do not highlight
    if ( rect != null ) {
      rect.setAttribute( "display", "none");
    }
    return;
  }

  
  var valueBounds = valueElement.getBBox();
  var width = valueElement.textLength.baseVal.value;
  var height = valueBounds.height;
  var x = -1 * width;
  var y = valueBounds.y;
  var rectWidth = width;
  var rectHeight = height;
  var rectColor = seriesNameElement.style.fill;

  //draw in rectangle if not there
 
  
  if ( rect == null ) {
    rect = document.createElementNS("http://www.w3.org/2000/svg",'rect');
    rect.setAttribute( "id", legendRectangleItemId );
    rect.setAttribute( "fill", rectColor );
    var oldChild = groupElement.removeChild( legendItemElement );
    groupElement.appendChild( rect );
    groupElement.appendChild( oldChild );
  }

  rect.setAttribute( "x", x );
  rect.setAttribute( "y", y );
  rect.setAttribute( "width", rectWidth );
  rect.setAttribute( "height", rectHeight );
  rect.setAttribute( "display", "inherit");


});


var existingChartOptions = null;
var groupingUnits = [[
                'week',                         // unit name
                [1]                             // allowed multiples
            ], [
                'month',
                [1, 2, 3, 4, 6]
            ]];



function getDefaultFontSize() {
  return "1em";
}

function getDefaultTitleFontSize() {
  return "1.25em";
}

function getDefaultFontFamily() {
  return "Tahoma, sans-serif";
}

function createGraph( command ) {
    graphCommand = command;
  var type = command.graphType;
  var graph = null;
  try {
    switch (type) {
      case "OHLC":
        graph = Highcharts.StockChart( createDefaultOptions("candlestick", command ) );
        break;
      case "Spread":
        graph = Highcharts.StockChart( createDefaultOptions("areasplinerange", command ) );
      break;
      default:
        graph = Highcharts.StockChart( createDefaultOptions("line", command ) );
    }
  } finally {
    document.title = "PC (" + command.title + ")";    
  }
  graph.publishListeners = [];
  graph.zoomedTimes = [];
  graph.redrawQueue = [];
  return graph;
}

var merge = Highcharts.merge;


function processOptions( parameters ) {
  var options = mergeOptions( parameters );
  parseBusinessHours( options );
  return options;
}


function defaultXAxisPositioner() {
  var positions = [];
  positions.info = this.tickPositions.info;
  this.tickPositions.forEach(function (position) {
    positions.push(position);
  });

  var padding = Math.floor((this.max - this.min ) * 0.03);
  var lastPosition = this.max + padding;

  positions.push(lastPosition);
  //store this so we can wack it later
  if ( isValued( positions.info ) ) {
    positions.info.paddingTick = lastPosition;
  }
        

  return positions;
}

function createDefaultOptions(chartType, command ) {
    var titleYOffset = 0;
    var legendYOffset = 13;
  var options;
  options = {
    pilotConfig: command,
    mergedOptions: processOptions(command.parameters),
    chart: {
      zoomType: 'xy',
      alignTicks: false,
      type: chartType,
      animation: false,
      reflow: true,
      renderTo: 'highChartsContainer',
      resetZoomButton: {
        theme: {
          display: 'none'
        }
      },
      spacingTop: 8,
      spacingRight: 5,
      spacingLeft: 2,
      backgroundColor: "#000000",
      plotBackgroundColor: "#000000",
      style: {
        fontFamily: getDefaultFontFamily(),
        fontSize: getDefaultFontSize()
      },
      events: {
        load: function (event) {
          event.target.showConnected();
        },
        selection: function (event) {
          var localChart = this;
          if (event.xAxis) {
            event.target.options.manuallyZoomed = true;
            localChart.redrawQueue.push( function() {
              localChart.handleManualZoomEvent();
            });
            localChart.queuePercentile();
             

          }
          this.recalculateTitle();
          //we should pass in the interested axis points from here instead
          //of waiting for highcharts to finish

        },
        load: onChartLoad,
        redraw: onChartRedraw
      },
    },
    global: {
      useUTC: true,
      timezoneOffset: new Date().getTimezoneOffset()
    },
    rangeSelector: {
      enabled: false
    },
    plotOptions: {
      series: {
        animation: false,        
        events: {
            mouseOver: function () {
                resetMouseWatchDogTimer();
            },
        },
      },
      candlestick: {
        color: 'green',
        upColor: 'red',
        dataGroupng: groupingUnits,
      }
    },

    navigator: {
      enabled: false,
      series: {
        includeInCSVExport: false
      },
      //height: 0,
      //margin: 0,
      xAxis: {
        visible: false
      }
    },

    exporting: {
      enabled: false,
      csv: {
        dateFormat: '%Y-%m-%d %H:%M:%S.%L'
      },
      chartOptions: { // specific options for the exported image
        plotOptions: {
          series: {
            dataLabels: {
              enabled: true
            }
          }
        }
      },
      scale: 3,
      fallbackToExportServer: false
    },
    scrollbar: {
      enabled: false
    },

    tooltip: {
	    enabled: true,
	    //pointFormat: "<span style=\"color:{point.color}\">●</span> {series.name}: <b>{point.y:.3f}</b><br/>",
      dateTimeLabelFormats: dateTimeFormats,
      crosshairs: [true, true]
    },

    title: {
      align: "center",
      verticalAlign: "top",
      floating: true,
      style: {
        font: "bold " + getDefaultTitleFontSize() + " " + getDefaultFontFamily()
      },
      text: command.title
    },
    subtitle: {
      align: "center",
      verticalAlign: "top",
      floating: true,
      style: {
        font: getDefaultFontSize() + " " + getDefaultFontFamily()
      }

    },
    legend: {
      enabled: true,
      floating: true,
      useHTML: false,
      rtl: true,
        // symbolHeight: 0,
        // symbolWidth: 0,
        // symbolRadius: 0,      
      verticalAlign: "top",
      backgroundColor: "#202020",
	    layout: "vertical",
      align: "left",
	    draggable: true,
	    lastValues: {},
	    title : {
		    text: "-",
		    style : {
          color: "white",
          //weight: "bold"
        }
	    },
      opacity: 0.90,
      itemStyle: {
        font: "bolder " + getDefaultFontSize() + " " + getDefaultFontFamily(),
      },

      //labelFormat:'<span style="color:{color}">{name}</span>: {point.y:.3f}<br/>',
      labelFormatter: function() {
        function errorMessageResolver( series, lastValue ) {
          if ( lastValue == null && series.userOptions.errorMessage ) {
            return series.userOptions.errorMessage;
          }
          return lastValue;
        }

        function extractValue( value ) {
          if ( Array.isArray( value ) ) {
            return value;
          }

          if ( typeof value == "number") {
            return value;
          }

          if ( isValued( value ) == false ) {
            return null;
          }

          if ( isValued( value.open ) ) {
            return [ value.open, value.high, value.low, value.close ];
          }
          if ( isValued( value.y ) ) {
            return value.y;
          }

          return null;
        }

        var lastValue = null;
        //check for hovering points
        var hoverPoints = this.chart.legend.getHoverPoints();
        var currentPoint = this.point;
        var color1 = this.color;
        var valueBackground = this.backgroundColor;
        var valueForeground = this.color;
        if ( hoverPoints && hoverPoints.length > 0 ) {
          var match = null;
          var linkedSeriesName = null;
          if ( this.linkedSeries.length > 0 ) {
            linkedSeriesName = this.linkedSeries[ 0 ].name;
          }

          for ( var index = 0; index < hoverPoints.length; index++ ) {
            var hoverPoint = hoverPoints[ index ];
            if ( hoverPoint.series.name == this.name || hoverPoint.series.name == linkedSeriesName ) {
              match = hoverPoint;
              break;
            }
          }
          if ( match != null ) {
            currentPoint = match;
          }
        }

        if ( isValued( currentPoint )  ) {
          lastValue = extractValue( currentPoint );
        }
        if ( lastValue == null && this.yData.length > 0 ) {
          //make current series color the background color and find the inverse color for the foreground
          valueBackground = this.color;
          valueForeground = getSeriesFGColor(this.color);
          lastValue = extractValue( this.yData[ this.yData.length - 1 ] );
        }

        //we are in a historical candlestick situation where we reach back into the linked series for its value
        if ( lastValue == null && this.linkedSeries.length > 0 ) {
          //go to historical series for value
          var linkedSeries = this.linkedSeries[ 0 ];
          if ( linkedSeries.yData.length > 0 ) {
            lastValue = extractValue( linkedSeries.yData[ linkedSeries.yData.length - 1 ]  );
          }
        }

        return legendTxtFormatter(color1, this.name, valueBackground, valueForeground, errorMessageResolver( this, lastValue ) )
      },
      
      borderWidth: 1,
      symbolWidth: 0,
      symbolHeight: 0,
      symbolPadding: 0,
      x: function( param ) {
        return 0;
      },
      y: 0
    },
    
    xAxis: [{
      crosshair: false,
      gridLineColor: '#618661',
      gridLineDashStyle: 'dot',
      gridLineWidth: 1,
      lineWidth: 1,
      labels: {
        style: {
          color: "#FFFFFF",
          fontSize: getDefaultFontSize()
        }
      },
      ordinal: true,
      startOnTick: false,
      showLastLabel: false,
      endOnTick: true,
      minTickInterval: 250,
      tickPositioner: defaultXAxisPositioner,
    }],
    performance: {
      currentPriceIndicator: true,
      seriesTooltips: true
    },
    series: []
  };
  options.yAxis = createAxisOptions( 'yDefaultAxis', {} );
    options.yAxis.id = "yDefaultAxis";
    options.yAxis.gridLineColor = "#618661";
    options.yAxis.gridLineWidth = 1;

  return options;
}

function getLegendValue( value ) {
  var valueString = "";
  if ( isValued( value ) == false ) {
    return "";
  }

  if ( typeof value == "string") {
    return value;
  }
  if ( Array.isArray( value ) == false ) {
     valueString = value.toFixed( 3 );
     return valueString;
  }  
  var index = 0;
  var ohlc = value.length == 4;
  value.forEach( function( member ) {
    if ( valueString.length > 0 ) {
      valueString = valueString + ",";
    }
    valueString = valueString + member.toFixed( 3 );
  });

  return "(" + valueString + ")";
}

var legendSeriesFormat = "<span style=\"float: right;display: inline-flex;white-space: nowrap;color:{0}\">{1} : <span style=\"float: right;display: inline-flex;white-space: nowrap;background-color:{2}; color:{3}\">{4}</span></span><br/>";
function legendTxtFormatter(nameColor, seriesName, valBgColor, valFgColor, val) {
 

    return legendSeriesFormat.format(nameColor, seriesName, valBgColor, valFgColor, getLegendValue( val ) );
}

function onChartLoad( event ) {
  var chart = this;

  function setupZoomPointer() {
    var pointer = chart.pointer;

    var fndragStart = pointer.dragStart;
    var fnDrop = pointer.drop;
    var oldZoomVert = pointer.zoomVert;
    var oldZoomHor = pointer.zoomHor;

    var updatedDragStart = function( e ) {
      if ( e.chartY > chart.plotTop + chart.plotHeight && pointer.zoomVert ) {
        //in the xaxis, turn off y axis zooming
        pointer.zoomVert = false;
        //put the click inside the plot
        e.chartY =  chart.plotHeight  + chart.plotTop - 1;
      } else if ( e.chartX < chart.plotLeft ) {
        pointer.zoomHor = false;
        e.chartX = chart.plotLeft;
      }
      fndragStart.apply( pointer, arguments );
    }

    var updatedDrop = function( e ) {
      fnDrop.apply( this, arguments );
      if ( this.zoomVert != oldZoomVert ) {
        this.zoomVert = oldZoomVert;
      }
      if ( this.zoomHor != oldZoomHor ) {
        this.zoomHor = oldZoomHor;
      }
    }

    pointer.dragStart = updatedDragStart;
    pointer.drop = updatedDrop;
  }

  this.setOptionXAxisZoom();
  this.renderTo.addEventListener( "click", function() {
    this.focus();
  })
  this.renderTo.onkeydown=function( event ){ 
    var plus = [187, 61, 107 ];
    var minus = [ 189, 173, 109 ];

    if ( event.ctrlKey == true ) {
      var currentSize = chart.getCurrentFontSize();
      if ( plus.indexOf( event.keyCode ) >= 0 ) {
        currentSize++;
      } else if ( minus.indexOf( event.keyCode ) >= 0 ) {
        currentSize--;
      } else {
        return;
      }
      if ( currentSize > 1 ) {
        chart.changeFontSize( currentSize );
      }

    }
  }
  chart.menuGenerator = new PilotChartMenuGenerator( chart );
  setupZoomPointer();
}

function onChartRedraw( event ) {
  initParameters( this );
  var chart = event.target;
  chart.showConnectionStatus();
  if ( chart.legend ) {
    chart.legend.reposition( null, null );
  }

  while( chart.redrawQueue.length > 0  ) {
    console.log( "Firing queued redraw action." );
    var queueFunction = chart.redrawQueue.pop();
    queueFunction.call( chart );
  }

}

function formatYAxis(  ) {
  var currentAxis = this.axis;
  var significantDigits = isDefined( currentAxis["significantDigits"] ) ? currentAxis["significantDigits"] : AXIS_SIGNIFICANT_DIGITS;

  if ( this.isFirst ) {
    significantDigits = this.axis.getSignificantDigits();
    currentAxis[ 'significantDigits' ] = significantDigits;
  }

  if ( this.isLast ) {
    delete currentAxis[ 'significantDigits' ];
  }

  return NUMBER_UTILS.formatSignificant( this.value, significantDigits );
}

function createAxisOptions( axisName, options ) {
  var axisId = getYAxisId( axisName );

  var paneIndex = getOption( options, "paneIndex", 0);  
  var axisType = getOption( options, 'axisType', 'linear' );
  var title = getOption( options, 'title', '' );
  var left = getOption( options, 'left', true );
  var labelAlignment = left ? 'right' : 'left';
  var lineColor = getOption( options, 'lineColor', null );
  var options = {
        id: axisId,
        lineWidth: 2,
        type: axisType,
        crosshair: false,
        opposite: !left,
        title: {
            //text: axisName
            text: ' ',
            margin: 4
        },
        tickLength: 5,
        tickWidth: 1,
        gridLineDashStyle: "dot",
        gridLineWidth: 0,
        labels: {
          align: labelAlignment,
          x: left ? -18 : 18,
          y: 2,
          //format: "{value:"+axisLabelFormat+"}",
          formatter: formatYAxis,
          style: {
            fontSize: getDefaultFontSize()
          }
        },
        paneIndex: paneIndex,
        isX: false,
        endOnTick: false,
        minPadding: 0.0,
        startOnTick: false,
        showLastLabel: true,
        currentPriceIndicator: createLastPriceOptions()
    };
    
  if ( paneIndex == 1) {
    options.height = "20%";
    options.top = "80%";      
  }   

  if ( lineColor != null ) {
    options.lineColor = lineColor;
  }


  return options;
}


function createLastPriceOptions() {
  return {
    lineDashStyle: 'Solid',
    zIndex: 2,
    lineOpacity: 1.0,
    lineColor: "#FFFF00",
    style : {
      fontSize: getDefaultFontSize()
    }
  };
}

function setNavigatorVisibleSeries( chart ) {
	if ( !chart.scroller || chart.series.length == 0  ) return;
	var scroller = chart.scroller;
	var mainAxis = chart.xAxis[0];
	var mainExtremes = mainAxis.getExtremes();
	var scrollerExtremes = scroller.xAxis.getExtremes();
	if ( !chart.series[0].visible && mainExtremes.max < scrollerExtremes.max ) {
		mainAxis.setExtremes();
	}

}

function countAxes( chart, left ) {
  if ( chart == null ) return 0;
  var axes = chart.getAllYAxes();
  if ( axes == null ) return 0;
  var count = 0;
  for( index = 0; index < axes.length; index++ ) {
    var axis = axes[ index ];
    if ( axis.options.opposite == !left ) {
      count++;
    }
  }

  return count;
}

function getConfigRollingWindow( chart ) {
  var index;
  var windowCount;
  var parameter = chart.getOptionParameter([KEY_ROLLING_WINDOW]);
  var returnValue = {
    offset: null,
    label: null
  };

  if ( parameter != null ) {
    if ( parameter.toLowerCase() == "all" ) {
      return returnValue;
    }

    var targetType = getRangeType( parameter );
    if ( targetType == null ) {
      console.warn( "Invalid rolling window type", parameter );
      returnValue.offset = -1;
    } else {
      windowCount = parseFloat( parameter.substr( 0, parameter.length - 1) );
      returnValue.offset = getTimeOffset( windowCount, targetType);
      returnValue.label = getLiveWindowLabel( windowCount, targetType );
    }
  }
  return returnValue;
}

function setConfigRollingWindow( chart ) {
  var rollingWindow = getConfigRollingWindow( chart );
  if ( rollingWindow && rollingWindow.offset > 0 ) {
    chart.setLiveWindow( rollingWindow.offset, rollingWindow.label );
  }
}

/**
 * After the chart is loaded and rendered, we need to initialize parameters
 */
function initParameters( chart ) {
  if ( !chart.options.initialized ) {
    //you put this in the end and it will loop forever if anything here triggers a redraw
    chart.options.initialized = true;
    chart.credits.element.setAttribute( "display", "none");
    setConfigRollingWindow( chart );

    if ( chart.legend && chart.legend.group && chart.legend.group.element ) {
      var options = chart.legend.options;
      var opacity = "0.7";
      if ( "opacity" in options ) opacity = options.opacity;

      chart.legend.group.element.setAttribute( "opacity", opacity.toString() );
      chart.legend.regenerateTitle();
      chart.recalculateParameters();
    }

  }
}

function recreateGraph( command ) {
  var h = createGraph(command);
  new ChartingPercentile(h);
  h.intelligentAxis = new IntelligentAxis( h );
  var lastUpdated = getParameterByName( "lastUpdateTime" );
  if ( isValued( lastUpdated ) ) {
    var lastUpdatedNumber = parseFloat( lastUpdated.trim() );
    if ( !isNaN( lastUpdatedNumber )) {
      h.options.pilotVersion = new Date( lastUpdatedNumber );
    }
  }

  initParameters(h);
  return h;
}

function getChartTitle( config, functionLabels ) {
  var renderedLabel = "";
  if ( functionLabels != null ) {
    for ( var index = 0; index < functionLabels.length; index++ ) {
      renderedLabel += " [" + functionLabels[ index ] +"]";
    }
  }

  return config.title + renderedLabel;
}

function assignYAxis(label, options) {
  var yaxisId = getYAxisId(label);
  var yaxis = highCharts.get(yaxisId);

  if (!yaxis && label) {
    options.left =( highCharts.yAxis.length ) % 2 == 0;
    var axisOptions = createAxisOptions(label, options);
    highCharts.addAxis(axisOptions, false, false, false);
    // if (axisOptions.opposite) {
    //   highCharts.xAxis[0].width = highCharts.xAxis[0].width - 30;
    // }
    // redistributePanes();
  }
  return yaxisId;
}


